package com.basker.pisces.springcloud.validation;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Set;
import java.util.stream.Stream;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.CollectionUtils;

import com.basker.pisces.service.constant.ServiceConstants;
import com.basker.pisces.service.dto.IServiceResult;
import com.basker.pisces.service.dto.ServiceResultFactory;
import com.basker.pisces.springcloud.GlobalAspect;

@Aspect
public class ClientValidationInterceptor extends GlobalAspect {

    @Autowired(required = false)
    private Validator validator;

    @Around("@annotation(org.springframework.web.bind.annotation.PostMapping)&&!restController()")
    public Object validateBeforeInvoke(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!this.needValidation(joinPoint)) {
            return joinPoint.proceed();
        }

        Object[] args = joinPoint.getArgs();
        Object bodyArg = args[0];

        Set<ConstraintViolation<Object>> violations = this.validator.validate(bodyArg, new Class<?>[0]);

        if (!CollectionUtils.isEmpty(violations)) {
            Stream<String> messages = violations.stream().map(v -> v.getMessage());
            String message = String.join("", messages.toArray(s -> new String[s]));

            Class<?> returnType = this.getMethod(joinPoint).getReturnType();

            return ServiceResultFactory.build(returnType, ServiceConstants.StatusCode.JSR303_VIOLATION, message, null);
        } else {
            return joinPoint.proceed();
        }
    }

    private Method getMethod(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalStateException("joinPoint is not MethodSignature.");
        }

        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature.getMethod();
    }

    private boolean needValidation(ProceedingJoinPoint joinPoint) {
        // 没有验证器 不校验
        if (this.validator == null) {
            return false;
        }

        // 没有参数 不校验
        Object[] args = joinPoint.getArgs();
        Object bodyArg = args.length > 0 ? args[0] : null;
        if (bodyArg == null) {
            return false;
        }

        // 方法参数标有Valid注解 需要校验
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            return false;
        }

        Method method = this.getMethod(joinPoint);
        if (!IServiceResult.class.isAssignableFrom(method.getReturnType())) {
            return false;
        }

        Parameter[] parameters = method.getParameters();
        return AnnotatedElementUtils.hasAnnotation(parameters[0], Valid.class);
    }

}
